namespace Sirius.Mapper.Internals
{
    using System;
    using System.Collections.Generic;
    using Exceptions;

    internal class TypeMapping<T> : ITypeMapping<T>
    {
        private readonly IDictionary<string, IPropertyMapper<T>> properties = new Dictionary<string, IPropertyMapper<T>>();
        private IPropertyMapper<T> keyProperty;
        private string table;

        public ITypeMapping<T> Property<TResult>(string columnName, Func<T, TResult> getter, Action<T, TResult> setter)
        {
            if (string.IsNullOrEmpty(columnName))
            {
                throw new ArgumentNullException("columnName");
            }

            if (getter == null)
            {
                throw new ArgumentNullException("getter");
            }

            if (setter == null)
            {
                throw new ArgumentNullException("setter");
            }

            if (properties.ContainsKey(columnName))
            {
                properties.Remove(columnName);

                if(keyProperty != null && keyProperty.Name == columnName)
                {
                    keyProperty = null;
                }
            }

            properties.Add(columnName, new PropertyMapper<T, TResult>(columnName, getter, setter));

            return this;
        }

        public ITypeMapping<T> Key<TResult>(string columnName, Func<T, TResult> getter, Action<T, TResult> setter)
        {
            if (string.IsNullOrEmpty(columnName))
            {
                throw new ArgumentNullException("columnName");
            }

            if (getter == null)
            {
                throw new ArgumentNullException("getter");
            }

            if (setter == null)
            {
                throw new ArgumentNullException("setter");
            }

            if (keyProperty != null && properties.ContainsKey(keyProperty.Name))
            {
                properties.Remove(keyProperty.Name);
            }

            Property(columnName, getter, setter);

            keyProperty = new PropertyMapper<T, TResult>(columnName, getter, setter);

            return this;
        }

        public ITypeMapping<T> Table(string tableName)
        {
            if (string.IsNullOrEmpty(tableName))
            {
                throw new ArgumentNullException("tableName");
            }

            table = tableName;
            return this;
        }

        public void Fill(T item, IDataSource dataSource)
        {
            foreach (var propertyMapper in properties.Values)
            {
                var expectedType = propertyMapper.PropertyType;
                var isNullable = expectedType.IsGenericType && expectedType.GetGenericTypeDefinition() == typeof(Nullable<>);

                if (isNullable)
                {
                    expectedType = expectedType.GetGenericArguments()[0];
                }

                var value = dataSource[propertyMapper.Name];
                if(value != null)
                {
                    var actualType = value.ObjectType;
                    if (actualType != expectedType)
                    {
                        throw new PropertyTypeMissmatchException(actualType, expectedType);
                    }

                    propertyMapper.Setter(item, value.ObjectValue);
                }
                else if(isNullable)
                {
                    propertyMapper.Setter(item, null);
                }
            }
        }

        public IQuery BuildInsertQuery(IProvider provider, T entity)
        {
            if (Equals(entity,null))
            {
                throw new ArgumentNullException("entity");
            }

            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

            if (properties.Count == 0)
            {
                throw new ParameterEmptyException("properties");
            }

            if(string.IsNullOrEmpty(table))
            {
                throw new ParameterEmptyException("table");
            }

            if (keyProperty == null)
            {
                throw new ParameterEmptyException("keyProperty");
            }

            var propertyList = new List<string>();
            foreach (var propertyMapper in properties.Values)
            {
                if (propertyMapper.Equals(keyProperty))
                    continue;

                propertyList.Add(propertyMapper.Name);
            }

            var query = provider.BuildInsertQuery(table, keyProperty.Name, propertyList);
            FillQuery(query, entity);
            return query;
        }

        public IQuery BuildDeleteQuery(IProvider provider, T entity)
        {
            if (Equals(entity, null))
            {
                throw new ArgumentNullException("entity");
            }

            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

            if (string.IsNullOrEmpty(table))
            {
                throw new ParameterEmptyException("table");
            }

            if (keyProperty == null)
            {
                throw new ParameterEmptyException("keyProperty");
            }

            var query = provider.BuildDeleteQuery(table, keyProperty.Name);
            FillQuery(query, entity);
            return query;
        }

        public IQuery BuildGetQuery(IProvider provider, T entity)
        {
            if (Equals(entity, null))
            {
                throw new ArgumentNullException("entity");
            }

            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

            if (keyProperty == null)
            {
                throw new ParameterEmptyException("keyProperty");
            }

            if (string.IsNullOrEmpty(table))
            {
                throw new ParameterEmptyException("table");
            }
            
            var query = provider.BuildGetQuery(table, keyProperty.Name);
            FillQuery(query, entity);
            return query;
        }

        public IQuery BuildUpdateQuery(IProvider provider, T entity)
        {
            if (Equals(entity, null))
            {
                throw new ArgumentNullException("entity");
            }


            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

            if (keyProperty == null)
            {
                throw new ParameterEmptyException("keyProperty");
            }

            if (string.IsNullOrEmpty(table))
            {
                throw new ParameterEmptyException("table");
            }

            var propertyList = new List<string>();
            foreach (var propertyMapper in properties.Values)
            {
                if(propertyMapper.Equals(keyProperty))
                    continue;

                propertyList.Add(propertyMapper.Name);
            }

            if (propertyList.Count == 0)
            {
                throw new ParameterEmptyException("properties");
            }

            var query = provider.BuildUpdateQuery(table, keyProperty.Name, propertyList);
            FillQuery(query, entity);
            return query;
        }

        private void FillQuery(IQuery query, T entity)
        {
            if (query == null)
            {
                throw new ArgumentNullException("query");
            }

            if (Equals(entity, default( T )))
            {
                throw new ArgumentNullException("entity");
            }

            var enumerator = query.GetEnumerator();

            if (enumerator == null)
                return;

            while(enumerator.MoveNext())
            {
                var property = enumerator.Current;

                if (!properties.ContainsKey(property.Name))
                {
                    throw new PropertyNotFoundException(property.Name);
                }

                var propertyMapper = properties[property.Name];
                property.Value = propertyMapper.Getter(entity);
            }
        }
    }
}